import 'dart:convert';

import 'package:agora_rtc_engine/src/agora_base.dart';
import 'package:agora_rtc_engine/src/agora_media_base.dart';
import 'package:agora_rtc_engine/src/agora_media_player.dart';
import 'package:agora_rtc_engine/src/agora_media_player_source.dart';
import 'package:agora_rtc_engine/src/agora_media_player_types.dart';
import 'package:agora_rtc_engine/src/render/media_player_controller.dart';
import 'package:flutter/services.dart';

/// Implementation of [MediaPlayerController]
class MediaPlayerOhosImpl implements MediaPlayer {
  MediaPlayerOhosImpl._(this._mediaPlayerId) {
    playerMethodChannel = const MethodChannel("agora_rtc_ng/media_player");
    playerMethodChannel.setMethodCallHandler((call) async {
      if (call.method == 'onPlayerStateChanged') {
        print(
            "agora_rtc_ng callHandler onPlayerStateChanged ${call.arguments.toString()}");
        int state = int.parse(call.arguments['state']);
        MediaPlayerState stateType = MediaPlayerState.playerStateIdle;
        for (var e in MediaPlayerState.values) {
          if (e.value() == state) {
            stateType = e;
            break;
          }
        }
        int reason = int.parse(call.arguments['reason']);
        MediaPlayerReason reasonType = MediaPlayerReason.playerReasonNone;
        for (var e in MediaPlayerReason.values) {
          if (e.value() == reason) {
            reasonType = e;
            break;
          }
        }
        _eventHandler.forEach((element) {
          element.onPlayerSourceStateChanged?.call(stateType, reasonType);
        });
        return true;
      } else if (call.method == 'onPositionChanged') {
        print(
            "agora_rtc_ng callHandler onPositionChanged ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onPositionChanged?.call(
              int.parse(call.arguments['positionMs']),
              int.parse(call.arguments['timestampMs']));
        });
        return true;
      } else if (call.method == 'onPlayerEvent') {
        print("agora_rtc_ng callHandler onPlayerEvent ${call.arguments.toString()}");
        int eventCode = int.parse(call.arguments['eventCode']);
        MediaPlayerEvent eventCodeType =
            MediaPlayerEvent.playerEventTryOpenStart;
        for (var e in MediaPlayerEvent.values) {
          if (e.value() == eventCode) {
            eventCodeType = e;
            break;
          }
        }
        _eventHandler.forEach((element) {
          element.onPlayerEvent?.call(
              eventCodeType,
              int.parse(call.arguments['elapsedTime']),
              call.arguments['message']);
        });
        return true;
      } else if (call.method == 'onMetaData') {
        print("agora_rtc_ng callHandler onMetaData ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onMetaData?.call(call.arguments['data'], 0);
        });
        return true;
      } else if (call.method == 'onPlayBufferUpdated') {
        print(
            "agora_rtc_ng callHandler onPlayBufferUpdated ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onPlayBufferUpdated
              ?.call(int.parse(call.arguments['playCachedBuffer']));
        });
        return true;
      } else if (call.method == 'onPreloadEvent') {
        print("agora_rtc_ng callHandler onPreloadEvent ${call.arguments.toString()}");
        int event = int.parse(call.arguments['event']);
        PlayerPreloadEvent eventType =
            PlayerPreloadEvent.playerPreloadEventBegin;
        for (var e in PlayerPreloadEvent.values) {
          if (e.value() == event) {
            eventType = e;
            break;
          }
        }
        _eventHandler.forEach((element) {
          element.onPreloadEvent?.call(call.arguments['src'], eventType);
        });
        return true;
      } else if (call.method == 'onPlayerSrcInfoChanged') {
        print(
            "agora_rtc_ng callHandler onPlayerSrcInfoChanged ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onPlayerSrcInfoChanged?.call(
              SrcInfo.fromJson(jsonDecode(call.arguments['from'])),
              SrcInfo.fromJson(jsonDecode(call.arguments['to'])));
        });
        return true;
      } else if (call.method == 'onPlayerInfoUpdated') {
        print(
            "agora_rtc_ng callHandler onPlayerInfoUpdated ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onPlayerInfoUpdated?.call(
              PlayerUpdatedInfo.fromJson(jsonDecode(call.arguments['info'])));
        });
        return true;
      } else if (call.method == 'onPlayerCacheStats') {
        print(
            "agora_rtc_ng callHandler onPlayerCacheStats ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onPlayerCacheStats?.call(
              CacheStatistics.fromJson(jsonDecode(call.arguments['stats'])));
        });
        return true;
      } else if (call.method == 'onPlayerPlaybackStats') {
        print(
            "agora_rtc_ng callHandler onPlayerPlaybackStats ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onPlayerPlaybackStats?.call(PlayerPlaybackStats.fromJson(
              jsonDecode(call.arguments['stats'])));
        });
        return true;
      } else if (call.method == 'onAudioVolumeIndication') {
        print(
            "agora_rtc_ng callHandler onAudioVolumeIndication ${call.arguments.toString()}");
        _eventHandler.forEach((element) {
          element.onAudioVolumeIndication
              ?.call(int.parse(call.arguments['volume']));
        });
        return true;
      }
      return false;
    });
  }

  final int _mediaPlayerId;
  late MethodChannel playerMethodChannel;
  final List<MediaPlayerSourceObserver> _eventHandler = [];

  /// Create the [MediaPlayerOhosImpl]
  MediaPlayerOhosImpl.create(int mediaPlayerId, MethodChannel methodChannel)
      : this._(mediaPlayerId);

  @override
  Future<void> adjustPlayoutVolume(int volume) async {
    playerMethodChannel.invokeMethod(
        "RtcEngine_adjustPlayoutVolume", {"volume": volume.toString()});
  }

  @override
  Future<void> adjustPublishSignalVolume(int volume) async {
    playerMethodChannel.invokeMethod(
        "RtcEngine_adjustPublishSignalVolume", {"volume": volume.toString()});
  }

  @override
  Future<void> enableAutoSwitchAgoraCDN(bool enable) async {
    playerMethodChannel
        .invokeMethod("RtcEngine_enableAutoSwitchAgoraCDN", {"enable": enable});
  }

  @override
  Future<int> getAgoraCDNLineCount() {
    return playerMethodChannel
        .invokeMethod<int>("RtcEngine_getAgoraCDNLineCount")
        .then<int>((int? value) => value ?? -1);
  }

  @override
  Future<int> getCurrentAgoraCDNIndex() {
    return playerMethodChannel
        .invokeMethod<int>("RtcEngine_getCurrentAgoraCDNIndex")
        .then<int>((int? value) => value ?? -1);
  }

  @override
  Future<int> getDuration() {
    return playerMethodChannel
        .invokeMethod<int>("RtcEngine_getDuration")
        .then<int>((int? value) => value ?? -1);
  }

  @override
  int getMediaPlayerId() {
    return _mediaPlayerId;
  }

  @override
  Future<bool> getMute() {
    return playerMethodChannel
        .invokeMethod<bool>("RtcEngine_getMute")
        .then<bool>((bool? value) => value ?? false);
  }

  @override
  Future<int> getPlayPosition() {
    return playerMethodChannel
        .invokeMethod<int>("RtcEngine_getPlayPosition")
        .then<int>((int? value) => value ?? 0);
  }

  @override
  Future<String> getPlaySrc() {
    return playerMethodChannel
        .invokeMethod<String>("RtcEngine_getPlaySrc")
        .then<String>((String? value) => value ?? "");
  }

  @override
  Future<String> getPlayerSdkVersion() {
    return playerMethodChannel
        .invokeMethod<String>("RtcEngine_getPlayerSdkVersion")
        .then<String>((String? value) => value ?? "");
  }

  @override
  Future<int> getPlayoutVolume() {
    return playerMethodChannel
        .invokeMethod<int>("RtcEngine_getPlayoutVolume")
        .then<int>((int? value) => value ?? 0);
  }

  @override
  Future<int> getPublishSignalVolume() {
    return playerMethodChannel
        .invokeMethod<int>("RtcEngine_getPublishSignalVolume")
        .then<int>((int? value) => value ?? 0);
  }

  @override
  Future<MediaPlayerState> getState() async {
    int state =
        await playerMethodChannel.invokeMethod<int>("RtcEngine_getState") ?? 0;
    MediaPlayerState stateType = MediaPlayerState.playerStateIdle;
    for (var e in MediaPlayerState.values) {
      if (e.value() == state) {
        stateType = e;
        break;
      }
    }
    return stateType;
  }

  @override
  Future<int> getStreamCount() {
    return playerMethodChannel
        .invokeMethod<int>("RtcEngine_getStreamCount")
        .then<int>((int? value) => value ?? 0);
  }

  @override
  Future<PlayerStreamInfo> getStreamInfo(int index) async {
    final Map<String, dynamic> args = {
      "index": index.toString(),
    };
    String info = await playerMethodChannel.invokeMethod<String>(
            "RtcEngine_getStreamInfo", args) ??
        "{}";
    return PlayerStreamInfo.fromJson(jsonDecode(info));
  }

  @override
  Future<void> mute(bool muted) async {
    final Map<String, dynamic> args = {
      "muted": muted,
    };
    playerMethodChannel.invokeMethod("RtcEngine_mute", args);
  }

  @override
  Future<void> open({required String url, required int startPos}) async {
    final Map<String, dynamic> args = {
      "url": url,
      "startPos": startPos.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_open", args);
  }

  @override
  Future<void> openWithAgoraCDNSrc(
      {required String src, required int startPos}) async {}

  @override
  Future<void> openWithMediaSource(MediaSource source) async {
    final Map<String, dynamic> args = {
      "source": jsonEncode(source.toJson()),
    };
    playerMethodChannel.invokeMethod("RtcEngine_openWithMediaSource", args);
  }

  @override
  Future<void> pause() async {
    playerMethodChannel.invokeMethod("RtcEngine_pause");
  }

  @override
  Future<void> play() async {
    playerMethodChannel.invokeMethod("RtcEngine_play");
  }

  @override
  Future<void> playPreloadedSrc(String src) async {
    final Map<String, dynamic> args = {
      "src": src,
    };
    playerMethodChannel.invokeMethod("RtcEngine_playPreloadedSrc", args);
  }

  @override
  Future<void> preloadSrc({required String src, required int startPos}) async {
    final Map<String, dynamic> args = {
      "src": src,
      "startPos": startPos.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_preloadSrc", args);
  }

  @override
  void registerAudioFrameObserver(
      {required AudioPcmFrameSink observer,
      RawAudioFrameOpModeType mode =
          RawAudioFrameOpModeType.rawAudioFrameOpModeReadOnly}) {}

  @override
  void registerMediaPlayerAudioSpectrumObserver(
      {required AudioSpectrumObserver observer, required int intervalInMS}) {}

  @override
  void registerPlayerSourceObserver(MediaPlayerSourceObserver observer) {
    _eventHandler.add(observer);
  }

  @override
  void registerVideoFrameObserver(MediaPlayerVideoFrameObserver observer) {}

  @override
  Future<void> renewAgoraCDNSrcToken(
      {required String token, required int ts}) async {}

  @override
  Future<void> resume() async {
    playerMethodChannel.invokeMethod("RtcEngine_resume");
  }

  @override
  Future<void> seek(int newPos) async {
    final Map<String, dynamic> args = {
      "newPos": newPos.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_seek", args);
  }

  @override
  Future<void> selectAudioTrack(int index) async {
    final Map<String, dynamic> args = {
      "index": index.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_player_selectAudioTrack", args);
  }

  @override
  Future<void> selectInternalSubtitle(int index) async {}

  @override
  Future<void> selectMultiAudioTrack(
      {required int playoutTrackIndex, required int publishTrackIndex}) async {
    final Map<String, dynamic> args = {
      "playoutTrackIndex": playoutTrackIndex.toString(),
      "publishTrackIndex": publishTrackIndex.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_selectMultiAudioTrack", args);
  }

  @override
  Future<void> setAudioDualMonoMode(AudioDualMonoMode mode) async {
    final Map<String, dynamic> args = {
      "mode": mode.value().toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_setAudioDualMonoMode", args);
  }

  @override
  Future<void> setAudioPitch(int pitch) async {
    final Map<String, dynamic> args = {
      "pitch": pitch.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_setAudioPitch", args);
  }

  @override
  Future<void> setExternalSubtitle(String url) async {}

  @override
  Future<void> setLoopCount(int loopCount) async {
    final Map<String, dynamic> args = {
      "loopCount": loopCount.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_setLoopCount", args);
  }

  @override
  Future<void> setPlaybackSpeed(int speed) async {
    final Map<String, dynamic> args = {
      "speed": speed.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_setPlaybackSpeed", args);
  }

  @override
  Future<void> setPlayerOptionInInt(
      {required String key, required int value}) async {}

  @override
  Future<void> setPlayerOptionInString(
      {required String key, required String value}) async {}

  @override
  Future<void> setRenderMode(RenderModeType renderMode) async {
    final Map<String, dynamic> args = {
      "renderMode": renderMode.value().toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_setRenderMode", args);
  }

  @override
  Future<void> setSoundPositionParams(
      {required double pan, required double gain}) async {}

  @override
  Future<void> setSpatialAudioParams(SpatialAudioParams params) async {}

  @override
  Future<void> setView(int view) async {
    final Map<String, dynamic> args = {
      "view": view.toString(),
    };
    playerMethodChannel.invokeMethod("RtcEngine_setView", args);
  }

  @override
  Future<void> stop() async {
    playerMethodChannel.invokeMethod("RtcEngine_stop");
  }

  @override
  Future<void> switchAgoraCDNLineByIndex(int index) async {}

  @override
  Future<void> switchAgoraCDNSrc(
      {required String src, bool syncPts = false}) async {}

  @override
  Future<void> switchSrc({required String src, bool syncPts = true}) async {
    final Map<String, dynamic> args = {
      "src": src,
      "syncPts": syncPts,
    };
    playerMethodChannel.invokeMethod("RtcEngine_switchSrc", args);
  }

  @override
  Future<void> takeScreenshot(String filename) async {}

  @override
  Future<void> unloadSrc(String src) async {
    final Map<String, dynamic> args = {
      "src": src,
    };
    playerMethodChannel.invokeMethod("RtcEngine_unloadSrc", args);
  }

  @override
  void unregisterAudioFrameObserver(AudioPcmFrameSink observer) {}

  @override
  void unregisterMediaPlayerAudioSpectrumObserver(
      AudioSpectrumObserver observer) {}

  @override
  void unregisterPlayerSourceObserver(MediaPlayerSourceObserver observer) {
    _eventHandler.remove(observer);
  }

  @override
  void unregisterVideoFrameObserver(MediaPlayerVideoFrameObserver observer) {}

  Future<void> destroy() async {
    playerMethodChannel.invokeMethod("RtcEngine_destroyMediaPlayer");
  }
}
